#include "fileref.h"
#include "napi/native_api.h"
#include "tpropertymap.h"
#include "napi_utils.cpp"
#include <cstdint>
#include <filemanagement/file_uri/error_code.h>
#include <filemanagement/file_uri/oh_file_uri.h>

static char *PathToUri(const char *path) {
    unsigned int length = strlen(path);
    char *pathResult = NULL;
    FileManagement_ErrCode ret = OH_FileUri_GetPathFromUri(path, length, &pathResult);
    if (ret == 0 && pathResult != NULL) {
        return pathResult;
    }
    return nullptr;
}

napi_value StringListToJsArray(napi_env env, const TagLib::StringList &stringList) {
    uint32_t length = static_cast<uint32_t>(stringList.size());
    napi_value jsArray;
    napi_create_array_with_length(env, length, &jsArray);

    for (int i = 0; i < length; i++) {
        const TagLib::String &str = stringList[i];
        const char *cStr = str.toCString(true);
        napi_value jsString;
        napi_create_string_utf8(env, cStr, NAPI_AUTO_LENGTH, &jsString);
        napi_set_element(env, jsArray, i, jsString);
    }

    return jsArray;
}

static napi_value NAPI_Global_getAudioProperties(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    size_t len = 0;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
    const std::unique_ptr<char[]> file_path = std::make_unique<char[]>(len + 1);
    napi_get_value_string_utf8(env, args[0], file_path.get(), len + 1, &len);
    char *path = PathToUri(file_path.get());
    const TagLib::FileRef f(path);
    free(path);
    if (f.isNull()) {
        return nullptr;
    }
    const auto props = f.audioProperties();
    props->bitrate();
    props->channels();
    props->lengthInMilliseconds();
    props->sampleRate();

    napi_value jsMap;
    napi_create_object(env, &jsMap);

    const int bitrate = props->bitrate();
    const int channels = props->channels();
    const int lengthMs = props->lengthInMilliseconds();
    const int sampleRate = props->sampleRate();

    napi_value jsLengthKey, jsLengthValue;
    napi_create_string_utf8(env, "length", NAPI_AUTO_LENGTH, &jsLengthKey);
    napi_create_int32(env, lengthMs, &jsLengthValue);
    napi_set_property(env, jsMap, jsLengthKey, jsLengthValue);

    napi_value jsBitrateKey, jsBitrateValue;
    napi_create_string_utf8(env, "bitrate", NAPI_AUTO_LENGTH, &jsBitrateKey);
    napi_create_int32(env, bitrate, &jsBitrateValue);
    napi_set_property(env, jsMap, jsBitrateKey, jsBitrateValue);

    napi_value jsSampleRateKey, jsSampleRateValue;
    napi_create_string_utf8(env, "sampleRate", NAPI_AUTO_LENGTH, &jsSampleRateKey);
    napi_create_int32(env, sampleRate, &jsSampleRateValue);
    napi_set_property(env, jsMap, jsSampleRateKey, jsSampleRateValue);

    napi_value jsChannelsKey, jsChannelsValue;
    napi_create_string_utf8(env, "channels", NAPI_AUTO_LENGTH, &jsChannelsKey);
    napi_create_int32(env, channels, &jsChannelsValue);
    napi_set_property(env, jsMap, jsChannelsKey, jsChannelsValue);

    return jsMap;
}

static napi_value NAPI_Global_getMetadata(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    auto file_path = napi_utils_get_string(env, args[0]);
    char *path = PathToUri(file_path.data());
    const TagLib::FileRef f(path);
    free(path);
    if (f.isNull()) {
        return nullptr;
    }
    const TagLib::PropertyMap prop = f.properties();
    napi_value jsMetadataMap;
    napi_create_object(env, &jsMetadataMap);
    for (const auto &property : prop) {
        const char *key = property.first.toCString(true);
        const TagLib::StringList &valueList = property.second;
        const napi_value array = StringListToJsArray(env, valueList);
        napi_value jsKey;
        napi_create_string_utf8(env, key, NAPI_AUTO_LENGTH, &jsKey);
        napi_set_property(env, jsMetadataMap, jsKey, array);
    }
    napi_value jsPictureArray;
    napi_create_array(env, &jsPictureArray);
    const TagLib::List<TagLib::VariantMap> pictureProp = f.complexProperties("PICTURE");
    for (int i = 0; i < pictureProp.size(); i++) {
        const TagLib::Map<TagLib::String, TagLib::Variant> picture = pictureProp[i];
        const TagLib::ByteVector pictureData = picture["data"].toByteVector();
        if (pictureData.isEmpty()) {
            continue;
        }
        napi_value jsPictureObj;
        napi_create_object(env, &jsPictureObj);
        const int length = pictureData.size();
        napi_value arraybuffer;
        void *buffer_data;
        napi_create_arraybuffer(env, length, &buffer_data, &arraybuffer);
        memcpy(buffer_data, pictureData.data(), length);
        napi_value jsPictureInt8Array;
        napi_create_typedarray(env, napi_int8_array, length, arraybuffer, 0, &jsPictureInt8Array);
        napi_value jsByteKey;
        napi_create_string_utf8(env, "data", NAPI_AUTO_LENGTH, &jsByteKey);
        napi_set_property(env, jsPictureObj, jsByteKey, jsPictureInt8Array);

        napi_value jsDescriptionKey;
        napi_create_string_utf8(env, "description", NAPI_AUTO_LENGTH, &jsDescriptionKey);
        const char *descriptionStr = picture["description"].toString().toCString();
        napi_value jsDescription;
        napi_create_string_utf8(env, descriptionStr, NAPI_AUTO_LENGTH, &jsDescription);
        napi_set_property(env, jsPictureObj, jsDescriptionKey, jsDescription);

        napi_value jsPictureTypeKey;
        napi_create_string_utf8(env, "pictureType", NAPI_AUTO_LENGTH, &jsPictureTypeKey);
        const char *pictureTypeStr = picture["pictureType"].toString().toCString();
        napi_value jsPictureType;
        napi_create_string_utf8(env, pictureTypeStr, NAPI_AUTO_LENGTH, &jsPictureType);
        napi_set_property(env, jsPictureObj, jsPictureTypeKey, jsPictureType);

        napi_value jsMimeTypeKey;
        napi_create_string_utf8(env, "mimeType", NAPI_AUTO_LENGTH, &jsMimeTypeKey);
        const char *mimeTypeStr = picture["mimeType"].toString().toCString();
        napi_value jsMimeType;
        napi_create_string_utf8(env, mimeTypeStr, NAPI_AUTO_LENGTH, &jsMimeType);
        napi_set_property(env, jsPictureObj, jsMimeTypeKey, jsMimeType);

        napi_set_element(env, jsPictureArray, i, jsPictureObj);
    }
    napi_value jsPictureKey;
    napi_create_string_utf8(env, "PICTURE", NAPI_AUTO_LENGTH, &jsPictureKey);
    napi_set_property(env, jsMetadataMap, jsPictureKey, jsPictureArray);

    return jsMetadataMap;
}

static napi_value NAPI_Global_saveMetadata(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    size_t len = 0;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
    auto file_path = napi_utils_get_string(env, args[0]);
    napi_value property_names;
    napi_get_all_property_names(env, args[1], napi_key_own_only, napi_key_skip_symbols, napi_key_keep_numbers,
                                &property_names);
    uint32_t props_name_length = 0;
    napi_get_array_length(env, property_names, &props_name_length);
    if (props_name_length == 0) {
        return nullptr;
    }
    char *path = PathToUri(file_path.data());
    const TagLib::FileRef f(path);
    free(path);
    if (f.isNull()) {
        return nullptr;
    }
    TagLib::PropertyMap props = f.properties();
    for (int index = 0; index < props_name_length; index++) {
        napi_value property_name_value;
        napi_get_element(env, property_names, index, &property_name_value);
        auto name = napi_utils_get_string(env, property_name_value);
        napi_value array_value;
        napi_get_property(env, args[1], property_name_value, &array_value);
        uint32_t array_length = 0;
        napi_get_array_length(env, array_value, &array_length);
        if (array_length == 0) {
            continue;
        }
        TagLib::StringList list = TagLib::StringList();
        for (int i = 0; i < array_length; i++) {
            napi_value value;
            napi_get_element(env, array_value, i, &value);
            auto key = napi_utils_get_string(env, value);
            list.append(TagLib::String(key, TagLib::String::UTF8));
        }
        props[TagLib::String(name, TagLib::String::UTF8)] = list;
    }
    f.file()->setProperties(props);
    f.file()->save();
    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"getAudioProperties", nullptr, NAPI_Global_getAudioProperties, nullptr, nullptr, nullptr, napi_default,
         nullptr},
        {"getMetadata", nullptr, NAPI_Global_getMetadata, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"saveMetadata", nullptr, NAPI_Global_saveMetadata, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "taglib",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterLibraryModule(void) { napi_module_register(&demoModule); }